package ynu.lcy.elememicroserviceuserauthservice9091.service;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import ynu.lcy.elememicroservicecommon.exception.BusinessException;
import ynu.lcy.elememicroservicecommon.utils.ErrorCode;
import ynu.lcy.elememicroserviceuserauthservice9091.chain.*;
import ynu.lcy.elememicroserviceuserauthservice9091.dao.IUserDao;
import ynu.lcy.elememicroserviceuserauthservice9091.entity.User;
import ynu.lcy.vo.TokenVo;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.concurrent.TimeUnit;

import static cn.hutool.core.lang.Console.log;
import static ynu.lcy.elememicroservicecommon.constant.CommonConstant.ACCESS_TOKEN;
import static ynu.lcy.elememicroservicecommon.constant.CommonConstant.REFRESH_TOKEN;
import static ynu.lcy.elememicroservicecommon.utils.TokenUtils.getToken;


@Service
public class AuthenticationService {

    private final AuthenticationHandler handlerChain;

    //accessToken过期时间
    private static final Integer ACCESS_Token_EXPIRE_TIME = 259200000;

    //refreshToken过期时间
    private static final Integer REFRESH_Token_EXPIRE_TIME = 259200000;

    //密钥
    private static final String SECRET = "ynu.edu.lcy";

    @Resource
    private IUserDao dao;

    @Resource
    private RedisTemplate redisTemplate;


    public AuthenticationService() {
        // 构建责任链
        AuthenticationHandler emptyCheckHandler = new EmptyCheckHandler();
        AuthenticationHandler phoneFormatHandler = new PhoneFormatHandler();
        AuthenticationHandler userAuthenticationHandler = new UserAuthenticationHandler();
        AuthenticationHandler accountLockCheckHandler = new AccountLockCheckHandler();

        emptyCheckHandler.setNextHandler(phoneFormatHandler);
        phoneFormatHandler.setNextHandler(userAuthenticationHandler);
        userAuthenticationHandler.setNextHandler(accountLockCheckHandler);

        this.handlerChain = emptyCheckHandler;
    }


    /**
     * 生成token
     *
     */
    public String createToken(User user, String tokenType){
        return JWT.create().withAudience(user.getUserPhone(),user.getId().toString()) //将userid保存到token里面作为载荷
                .withIssuedAt(new Date())
                .withIssuer("lcy") //签发人
                .withClaim("role",user.getUserRole())
                .withClaim("tokenType",tokenType)
                .sign(Algorithm.HMAC256(SECRET));
    }

    /**
     * 认证服务
     *
     * @param context 用户上下文
     * @return user
     * @throws BusinessException
     */
    public User authenticate(AuthenticationContext context) throws BusinessException {
        handlerChain.handle(context);
        return context.getUser();
    }

    /**
     * 校验token合法性
     *
     * @param token
     * @return DecodedJWT
     */
    public DecodedJWT validateToken(String token, String needTokenType){
        if (StringUtils.isBlank(token)) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        DecodedJWT decodedJWT = JWT.decode(token);
        //获取tokenType
        String tokenType = decodedJWT.getClaim("tokenType").asString();
        if (StringUtils.isBlank(tokenType) || !StringUtils.equals(tokenType,needTokenType)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //获取redisToken
        String userPhone = decodedJWT.getAudience().get(0);
        String redisToken = (String)redisTemplate.opsForValue().get(userPhone + tokenType);
        //判断是否过期
        if (StringUtils.isBlank(redisToken)){
            //如果accessToken过期，则获取最新token
            if (tokenType.equals(ACCESS_TOKEN)){
                throw new BusinessException(ErrorCode.AUTH_ERROR);
            }else {
                //如果refreshToken过期，则说明登录过期需要重新登陆
                throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
            }
        }
        //判断是否正确合法
        if (!StringUtils.equals(token,redisToken)) {
            //token非法
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        return decodedJWT;
    }

    /**
     * 通过解析token返回当前用户
     *
     * @param request   请求
     * @param tokenType token类型
     */
    public User getCurrentUser(HttpServletRequest request, String tokenType) {
        String token = getToken(request);
        DecodedJWT decodedJWT = validateToken(token,tokenType);
        String userPhone = decodedJWT.getAudience().get(0);
        log("userPhone: " + userPhone);
        return dao.findUserByuserPhone(userPhone);
    }

    /**
     * 把accessToken和refreshToken存入redis
     *
     * @param user         用户信息
     * @param accessToken  accessToken
     * @param refreshToken refreshToken
     */
    public void saveTokenToRedis(User user, String accessToken, String refreshToken) {
        //存入redis，过期时间为7days，key为userPhone+"_accessToken"
        redisTemplate.opsForValue().set(user.getUserPhone()+ACCESS_TOKEN,
                accessToken, ACCESS_Token_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        //存入redis，过期时间为30days，key为userPhone+"_refreshToken"
        redisTemplate.opsForValue().set(user.getUserPhone()+REFRESH_TOKEN,
                refreshToken, REFRESH_Token_EXPIRE_TIME, TimeUnit.MILLISECONDS);
    }

    /**
     * 通过refresh_token换取新的access_token
     * @param request 请求
     * @return token
     * */
    public TokenVo updateAccessToken(HttpServletRequest request) {
        //解析其中用户
        User user = getCurrentUser(request,REFRESH_TOKEN);
        //生成新的access_token
        String newAccessToken = createToken(user,ACCESS_TOKEN);
        //生成新的refresh_token
        String newRefreshToken = createToken(user, REFRESH_TOKEN);
        //存入redis
        saveTokenToRedis(user,newAccessToken, newRefreshToken);
        return new TokenVo(newAccessToken, newRefreshToken);
    }



}
